/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

using System;
using System.Diagnostics;
using NUnit.Framework;
using dnAnalytics.LinearAlgebra.Solvers;
using dnAnalytics.LinearAlgebra;

namespace dnAnalytics.Tests.LinearAlgebra.Solvers
{
    [TestFixture]
    public sealed class DivergenceStopCriteriumTest
    {
        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateWithNegativeMaximumIncrease()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(-0.1);
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateWithIllegalMinimumIterations()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium((int)2);
            Assert.Fail();
        }

        [Test]
        public void Create()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(0.1, 3);
            Assert.IsNotNull(criterium, "There should be a criterium");

            Assert.AreEqual(0.1, criterium.MaximumRelativeIncrease, "Incorrect maximum");
            Assert.AreEqual(3, criterium.MinimumNumberOfIterations, "Incorrect iteration count");
        }

        [Test]
        public void ResetMaximumIncrease()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(0.5, 3);
            Assert.IsNotNull(criterium, "There should be a criterium");

            Assert.AreEqual(0.5, criterium.MaximumRelativeIncrease, "Incorrect maximum");

            criterium.ResetMaximumRelativeIncreaseToDefault();
            Assert.AreEqual(DivergenceStopCriterium.DefaultMaximumRelativeIncrease, criterium.MaximumRelativeIncrease, "Incorrect value");
        }

        [Test]
        public void ResetMinimumIterationsBelowMaximum()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(0.5, 15);
            Assert.IsNotNull(criterium, "There should be a criterium");

            Assert.AreEqual(15, criterium.MinimumNumberOfIterations, "Incorrect iteration count");

            criterium.ResetNumberOfIterationsToDefault();
            Assert.AreEqual(DivergenceStopCriterium.DefaultMinimumNumberOfIterations, criterium.MinimumNumberOfIterations, "Incorrect value");
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DetermineStatusWithIllegalIterationNumber()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(0.5, 15);
            criterium.DetermineStatus(-1,
                                      new DenseVector(3, 4),
                                      new DenseVector(3, 5),
                                      new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullResidualVector()
        {
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(0.5, 15);
            criterium.DetermineStatus(1,
                                      new DenseVector(3, 4),
                                      new DenseVector(3, 5),
                                      null);
            Assert.Fail();
        }

        [Test]
        public void DetermineStatusWithTooFewIterations()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);

            // Add residuals. We should not diverge because we'll have to few iterations
            for (int i = 0; i < iterations - 1; i++)
            {
                criterium.DetermineStatus(i,
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { (i + 1) * (increase + 0.1) }));

                Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail.");
            }
        }

        [Test]
        public void DetermineStatusWithNoDivergence()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);

            // Add residuals. We should not diverge because we won't have enough increase
            for (int i = 0; i < iterations * 2; i++)
            {
                criterium.DetermineStatus(i,
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { (i + 1) * (increase - 0.01) }));

                Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail.");
            }
        }

        [Test]
        public void DetermineStatusWithDivergenceThroughNaN()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);

            // Add residuals. We should not diverge because we'll have to few iterations
            for (int i = 0; i < iterations - 5; i++)
            {
                criterium.DetermineStatus(i,
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { (i + 1) * (increase - 0.01) }));

                Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail.");
            }
            
            // Now make it fail by throwing in a NaN
            {
                criterium.DetermineStatus(iterations,
                                         new DenseVector(new double[] { 1 }),
                                         new DenseVector(new double[] { 1 }),
                                         new DenseVector(new double[] { double.NaN }));

                Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail."); 
            }
        }

        [Test]
        public void DetermineStatusWithDivergence()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);

            // Add residuals. We should not diverge because we'll have one to few iterations
            double previous = 1;
            for (int i = 0; i < iterations - 1; i++)
            {
                previous *= (1 + increase + 0.01);
                criterium.DetermineStatus(i,
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { previous }));

                Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail.");
            }

            // Add the final residual. Now we should have divergence
            {
                previous *= (1 + increase + 0.01);
                criterium.DetermineStatus(iterations - 1,
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { 1 }),
                                          new DenseVector(new double[] { previous }));

                Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail.");
            }
        }

        [Test]
        public void ResetCalculationState()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);

            // Add residuals. Blow it up instantly
            criterium.DetermineStatus(1,
                                      new DenseVector(new double[] { 1 }),
                                      new DenseVector(new double[] { 1 }),
                                      new DenseVector(new double[] { double.NaN }));

            Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail.");

            // Reset the state
            criterium.ResetToPrecalculationState();

            Assert.AreEqual(increase, criterium.MaximumRelativeIncrease, "Incorrect maximum");
            Assert.AreEqual(iterations, criterium.MinimumNumberOfIterations, "Incorrect iteration count");
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Status check fail.");
        }

        [Test]
        public void Clone()
        {
            double increase = 0.5;
            int iterations = 10;
            DivergenceStopCriterium criterium = new DivergenceStopCriterium(increase, iterations);
            Assert.IsNotNull(criterium, "There should be a criterium");

            IIterationStopCriterium clone = criterium.Clone();
            Assert.IsInstanceOfType(typeof(DivergenceStopCriterium), clone, "Wrong criterium type");

            DivergenceStopCriterium clonedCriterium = clone as DivergenceStopCriterium;
            Assert.AreEqual(criterium.MaximumRelativeIncrease, clonedCriterium.MaximumRelativeIncrease, "Incorrect maximum");
            Assert.AreEqual(criterium.MinimumNumberOfIterations, clonedCriterium.MinimumNumberOfIterations, "Incorrect iteration count");
        }
    }
}
